22.CS61C Lab3
Lab 3
Exercise 1: Familiarizing yourself with Venus
- What do the
.data,.word,.textdirectives mean (i.e. what do you use them for)? Hint: think about the 4 sections of memory.
这里提示我们了内存的四个部分
.data 表示内存数据上数据段的开始,
.word 2, 4, 6, 8 表示在内存中连续存储4个32位整数(2、4、6、8)
n: .word 9 定义了一个名为 n 的标签,指向一个值为9的32位整数,也就是 n=9
.text表示代码段(text section)的开始。在这个段中,程序存放实际执行的机器指令(如函数、主程序等)。
- Run the program to completion. What number did the program output? What does this number represent?
输出了
- At what address is
nstored in memory? Hint: Look at the contents of the registers.
通过观察寄存器 x28 我们可以得到 n 的地址是 0x10000010
- Without actually editing the code (i.e. without going into the “Editor” tab), have the program calculate the 13th fib number (0-indexed) by manually modifying the value of a register. You may find it helpful to first step through the code. If you prefer to look at decimal values, change the “Display Settings” option at the bottom.
答案是
Exercise 2: Translating from C to RISC-V
- The register representing the variable
k.
t0
- The register representing the variable
sum.
s0
- The registers acting as pointers to the
sourceanddestarrays.
la s1, source
la s2, dest
- The assembly code for the loop found in the C code.
for (k = 0; source[k] != 0; k++) {
dest[k] = fun(source[k]);
sum += dest[k];
}
- How the pointers are manipulated in the assembly code.
在汇编代码中,先通过 la 命令获取数组的首地址,然后通过 add t1, s1, s3 来获取到 source[i] 的值
Exercise 3: Factorial
在这个 Exercise 中,需要实现一个函数计算阶乘
.globl factorial
.data
n: .word 10
.text
main:
la t0, n
lw a0, 0(t0)
jal ra, factorial
addi a1, a0, 0
addi a0, x0, 1
ecall # Print Result
addi a1, x0, '\n'
addi a0, x0, 11
ecall # Print newline
addi a0, x0, 10
ecall # Exit
factorial:
# 需要实现一个函数计算阶乘
addi t0, x0, 1 # t0 = 1
addi t1, x0, 1 # t1 = 1
addi t2, x0, 1
beq a0, t2, end # if a0 == t2 end
beq a0, x0, end
loop:
mul t0, t0, t1 # t0 = t0 * t1
addi t1, t1, 1 # t1 += 1
bne t1, a0, loop # if t1 <= n loop
end:
mv a0, t0 # a0 = t0
ret
Exercise 4: Calling Convention Checker
这个练习需要你修复一个汇编代码,我们使用
$ java -jar tools/venus.jar -cc lab03/cc_test.s
命令能获得当前代码的一些问题
.globl simple_fn naive_pow inc_arr
.data
failure_message: .asciiz "Test failed for some reason.\n"
success_message: .asciiz "Sanity checks passed! Make sure there are no CC violations.\n"
array:
.word 1 2 3 4 5
exp_inc_array_result:
.word 2 3 4 5 6
.text
main:
# We test our program by loading a bunch of random values
# into a few saved registers - if any of these are modified
# after these functions return, then we know calling
# convention was broken by one of these functions
li s0, 2623
li s1, 2910
# ... skipping middle registers so the file isn't too long
# If we wanted to be rigorous, we would add checks for
# s2-s20 as well
li s11, 134
# Now, we call some functions
# simple_fn: should return 1
jal simple_fn # Shorthand for "jal ra, simple_fn"
li t0, 1
bne a0, t0, failure
# naive_pow: should return 2 ** 7 = 128
li a0, 2
li a1, 7
jal naive_pow
li t0, 128
bne a0, t0, failure
# inc_arr: increments "array" in place
la a0, array
li a1, 5
jal inc_arr
jal check_arr # Verifies inc_arr and jumps to "failure" on failure
# Check the values in the saved registers for sanity
li t0, 2623
li t1, 2910
li t2, 134
bne s0, t0, failure
bne s1, t1, failure
bne s11, t2, failure
# If none of those branches were hit, print a message and exit normally
li a0, 4
la a1, success_message
ecall
li a0, 10
ecall
# Just a simple function. Returns 1.
#
# FIXME Fix the reported error in this function (you can delete lines
# if necessary, as long as the function still returns 1 in a0).
simple_fn:
# mv a0, t0
li a0, 1
ret
# Computes a0 to the power of a1.
# This is analogous to the following C pseudocode:
#
# uint32_t naive_pow(uint32_t a0, uint32_t a1) {
# uint32_t s0 = 1;
# while (a1 != 0) {
# s0 *= a0;
# a1 -= 1;
# }
# return s0;
# }
#
# FIXME There's a CC error with this function!
# The big all-caps comments should give you a hint about what's
# missing. Another hint: what does the "s" in "s0" stand for?
naive_pow:
# BEGIN PROLOGUE
# END PROLOGUE
addi sp, sp, -8
sw ra, 4(sp)
sw s0, 0(sp)
li s0, 1
naive_pow_loop:
beq a1, zero, naive_pow_end
mul s0, s0, a0
addi a1, a1, -1
j naive_pow_loop
naive_pow_end:
mv a0, s0
lw s0, 0(sp)
lw ra, 4(sp)
addi sp, sp, 8
ret
# Increments the elements of an array in-place.
# a0 holds the address of the start of the array, and a1 holds
# the number of elements it contains.
#
# This function calls the "helper_fn" function, which takes in an
# address as argument and increments the 32-bit value stored there.
inc_arr:
addi sp, sp, -16
sw ra, 12(sp)
sw s0, 8(sp)
sw s1, 4(sp)
mv s0, a0 # Copy start of array to saved register
mv s1, a1 # Copy length of array to saved register
li t0, 0 # Initialize counter to 0
inc_arr_loop:
beq t0, s1, inc_arr_end
slli t1, t0, 2 # Convert array index to byte offset
add a0, s0, t1 # Add offset to start of exp_inc_array_result
sw t0, 0(sp)
jal helper_fn
lw t0, 0(sp)
addi t0, t0, 1 # Increment counter
j inc_arr_loop
inc_arr_end:
lw s1, 4(sp)
lw s0, 8(sp)
lw ra, 12(sp)
addi sp, sp, 16
ret
# This helper function adds 1 to the value at the memory address in a0.
# It doesn't return anything.
# C pseudocode for what it does: "*a0 = *a0 + 1"
#
# FIXME This function also violates calling convention, but it might not
# be reported by the Venus CC checker (try and figure out why).
# You should fix the bug anyway by filling in the prologue and epilogue
# as appropriate.
helper_fn:
addi sp, sp, -8
sw ra, 4(sp)
sw s0, 0(sp)
lw t1, 0(a0)
addi s0, t1, 1
sw s0, 0(a0)
lw s0, 0(sp)
lw ra, 4(sp)
addi sp, sp, 8
ret
# YOU CAN IGNORE EVERYTHING BELOW THIS COMMENT
# Checks the result of inc_arr, which should contain 2 3 4 5 6 after
# one call.
# You can safely ignore this function; it has no errors.
check_arr:
la t0, exp_inc_array_result
la t1, array
addi t2, t1, 20 # Last element is 5*4 bytes off
check_arr_loop:
beq t1, t2, check_arr_end
lw t3, 0(t0)
lw t4, 0(t1)
bne t3, t4, failure
addi t0, t0, 4
addi t1, t1, 4
j check_arr_loop
check_arr_end:
ret
# This isn't really a function - it just prints a message, then
# terminates the program on failure. Think of it like an exception.
failure:
li a0, 4 # String print ecall
la a1, failure_message
ecall
li a0, 10 # Exit ecall
ecall
- What caused the errors in
simple_fn,naive_pow, andinc_arrthat were reported by the Venus CC checker?
在 simple_fn 中,
在 naive_pow 中,使用了 s0(保存寄存器),但未在函数开头保存它
在 inc_arr 中,使用了 s0 和 s1(保存寄存器),但未保存它们。调用 helper_fn 前未保存临时寄存器 t0(虽然 t0 是临时寄存器,但跨函数调用时需手动保存)。
- In RISC-V, we call functions by jumping to them and storing the return address in the
raregister. Does calling convention apply to the jumps to thenaive_pow_loopornaive_pow_endlabels?
在 RISC-V 中,我们通过跳转到函数并将返回地址存储在ra寄存器中来调用函数。调用约定是否适用于跳转到naive_pow_loop函数? 或naive_pow_end标签?
不实用,ra 属于函数调用,而 naive_pow_loop 和 naive_pow_end 属于标签跳转
- Why do we need to store
rain the prologue forinc_arr, but not in any other function?
为什么我们需要将ra存储在inc_arr的序言中,而不是存储在任何其他函数中?
因为接下来需要调用 helper_fn 函数,必须在在开始的时候保存 ra
- Why wasn’t the calling convention error in
helper_fnreported by the CC checker? (Hint: it’s mentioned above in the exercise instructions.)
不知道,不会,大佬教教
Exercise 5: RISC-V function calling with map
.globl map
.text
main:
jal ra, create_default_list
add s0, a0, x0 # a0 = s0 is head of node list
#print the list
add a0, s0, x0
jal ra, print_list
# print a newline
jal ra, print_newline
# load your args
add a0, s0, x0 # load the address of the first node into a0
# load the address of the function in question into a1 (check out la on the green sheet)
la a1, square # 加载 square 函数的地址到 a1
# issue the call to map
jal ra, map
# print the list
add a0, s0, x0
jal ra, print_list
# print another newline
jal ra, print_newline
addi a0, x0, 10
ecall #Terminate the program
map:
# Prologue: Make space on the stack and back-up registers
addi sp, sp, -12 # 分配栈空间(保存 ra, s0, s1)
sw ra, 8(sp) # 保存返回地址
sw s0, 4(sp) # 保存 s0
sw s1, 0(sp) # 保存 s1
beq a0, x0, done # If we were given a null pointer (address 0), we're done.
add s0, a0, x0 # Save address of this node in s0
add s1, a1, x0 # Save address of function in s1
# load the value of the current node into a0
lw a0, 0(s0) # 加载当前节点的值到 a0(作为函数参数)
# Call the function in question on that value. DO NOT use a label (be prepared to answer why).
jalr ra, s1, 0 # 调用函数(地址在 s1 中),结果存储在 a0
# store the returned value back into the node
sw a0, 0(s0) # 将返回值存回当前节点的 value
# Load the address of the next node into a0
lw a0, 4(s0) # 加载下一个节点的地址到 a0
# Put the address of the function back into a1 to prepare for the recursion
add a1, s1, x0 # 将函数地址从 s1 恢复到 a1
# recurse
jal ra, map # 递归调用 map
done:
# Epilogue: Restore register values and free space from the stack
lw s1, 0(sp) # 恢复 s1
lw s0, 4(sp) # 恢复 s0
lw ra, 8(sp) # 恢复 ra
addi sp, sp, 12 # 释放栈空间
jr ra # Return to caller
square:
mul a0, a0, a0
jr ra
# ... (其余代码保持不变)